package glemipsc.instructions;

import glemipsc.AbsoluteAddress;
import glemipsc.Address;
import glemipsc.Function;
import glemipsc.GLEMipsC;
import glemipsc.RelativeAddress;
import glemipsc.SymbolElement;
import glemipsc.Type;
import glemipsc.Type.TypeFormat;
import glemipsc.implementations.Argument;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Switch extends BaseInstruction implements Serializable, Instruction, Expression {
	private Expression var;
	private ArrayList Instructions;
	private Case defaultCase;
	private glemipsc.implementations.Instruction exitAddressNop;
	
	public Switch(Expression var, ArrayList Instructions){
		this.var = var;
		this.Instructions = Instructions;
		Iterator iter = Instructions.iterator();
		while (iter.hasNext()) {
			Case currCase = (Case) iter.next();
			currCase.setParent(this);
			if (defaultCase == null && currCase.isDefaultCase()) {
				this.defaultCase = currCase;
			}
		}
		exitAddressNop = GLEMipsC.compiler.getEnvironment().implementation.getNop();
	}
	@Override
	public void computeInstruction(Argument result,
			List<glemipsc.implementations.Instruction> instructions,
			Function func) {
		glemipsc.implementations.Implementation impl = GLEMipsC.compiler.getEnvironment().implementation;
		
		// Building a new default case if no one is defined
		if (defaultCase == null) {
			ArrayList instructs = new ArrayList();
			instructs.add(new CycleControl("break"));
			defaultCase = new Case(instructs);
			defaultCase.setParent(this);
			Instructions.add(defaultCase);
		}
		
		int casesTab = func.getStackSize();
		
		int minCase = 0;
		int maxCase = 0;
		
		// Getting cases table bounds
		Iterator iter = Instructions.iterator();
		while (iter.hasNext()) {
			Case currCase = (Case) iter.next();
			if (currCase.isDefaultCase())
				continue;
			if (currCase.getConst() < minCase)
				minCase = currCase.getConst();
			if (currCase.getConst() > maxCase)
				maxCase = currCase.getConst();
		}
		
		int casesTableSize = maxCase - minCase + 1;
		
		// Preparing case addresses
		ArrayList switchCaseInstructions = new ArrayList();
		iter = Instructions.iterator();
		while (iter.hasNext()) {
			Case currCase = (Case) iter.next();
			currCase.setStoreAddress(new AbsoluteAddress(switchCaseInstructions.size()));
			currCase.computeInstruction(result, switchCaseInstructions, func);
		}
		
		// Preparing jumps table
		for (int i = 0; i < casesTableSize; i++) {
			Address addr = new RelativeAddress(impl.getStackPointerRegister(), casesTab + i);
			Case currCase = getCaseByValue(minCase + i);
			instructions.add(impl.getMov(result, currCase.getStoreAddress().getOffset()));
			instructions.add(impl.getSt(result, addr, impl.getRegistersBits() / 8));
		}
		
		// Evaluating switch argument
		var.computeInstruction(result, instructions, func);
		
		instructions.add(impl.getSub(result, result, impl.getImmediate(minCase), true));
		
		// In bounds check
		instructions.add(impl.getJlz(result, defaultCase.getStoreAddress().getOffset()));
		Argument tmp = impl.getFreeRegister();
		instructions.add(impl.getSub(tmp, result, impl.getImmediate(maxCase - minCase + 1), true));
		instructions.add(impl.getJgz(tmp, defaultCase.getStoreAddress().getOffset()));
		impl.freeRegister(tmp);
		
		// Jumping to the correct case
		instructions.add(impl.getAdd(result, result, impl.getStackPointerRegister(), true));
		instructions.add(impl.getLd(result, new RelativeAddress(result, casesTab), impl.getRegistersBits() / 8));
		instructions.add(impl.getJz(impl.getZeroRegister(), result));
		
		// Adding cases instructions
		instructions.addAll(switchCaseInstructions);
		
		instructions.add(exitAddressNop);
		
	}
	
	private Case getCaseByValue(int value) {
		Iterator iter = Instructions.iterator();
		while (iter.hasNext()) {
			Case currCase = (Case) iter.next();
			if (currCase.getConst() == value)
				return currCase;
		}
		return defaultCase;
	}

	@Override
	public int getNumberOfOperands() {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public Expression getOperand(int index) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public int getImmediateValue() {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public Address getStoreAddress() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public int getStoredSize() {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public Type getType() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean isImmediate() {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean isStored() {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public void setStoreAddress(Address addr) {
		// TODO Auto-generated method stub
		
	}
	
	public Address getBreakAddress() {
		return exitAddressNop.getAddress();
	}

}
